spring-framework-4-reference

15.5 JPA

Spring JPA,存在与 org.springframework.orm.jpa 包,提供方便的对于 Java Persistence API 的类似于 Hibernate 或者 JDO 的支持,为了解底层的实现,提供额外的功能。

15.5.1 Three options for JPA setup in a Spring environment 三种设置选项

Spring JPA 提供三种方式来设置 JPA EntityManagerFactory 用于应用程序实现实体的管理。

LocalEntityManagerFactoryBean

只在简单部署环境中,比如独立的应用程序和集成测试才使用该选项

LocalEntityManagerFactoryBean 创建了一个仅使用 JPA 访问数据适合部署在简单环境下的应用程序的 EntityManagerFactory。工厂 bean 使用 JPA PersistenceProvider 自动检测机制(根据 JPA 的Java SE 引导),在大多数情况下,需要指定唯一持久单元名称:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    </bean>
</beans>

这种形式的 JPA 的部署是最简单和最有限的。你不能引用现有的 JDBC DataSource 的 bean 的定义,并且不支持全局事务的存在。此外,织入(字节码转换)持久化类是提供者特定的,往往需要一个特定的 JVM 代理在启动时指定。此选项仅适用于为 JPA 规范设计的独立的应用程序和测试环境。

Obtaining an EntityManagerFactory from JNDI 从 JNDI 中获得 EntityManagerFactory

当部署在 Java EE 5 服务器中使用该选项,查看你的服务器的文档来获知如何部署自定义的 JPA 提供者 在你的服务器中,允许不同于服务器默认的提供者。

从 JNDI 中获得 EntityManagerFactory (举例 在 Java EE 5 环境中),只需简单配置 XML:

<beans>
    <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

这个动作指定标准的 Java EE 5 的引导: Java EE服务器自动检测持久单元(实际上,META-INF/persistence.xml文件在应用的 jar 中)和在Java EE部署描述符中的 persistence-unit-ref 的实体(例如,web.xml)并为这些定义环境命名上下文的位置。

在这种情况下,整个持久化单元的部署,包括织入(字节码转换)持久化类,到 Java EE 服务器。JDBC DataSource是通过 JNDI 位置定义在META-INF/persistence.xml文件中。EntityManager 事务集成在服务器的 JTA 子系统中。Spring 只是使用获得的 EntityManagerFactory,通过依赖注入传递给应用程序对象,并且为持久单元管理事务,通常是通过 JtaTransactionManager

如果多个持久单元中使用相同的应用程序, JNDI检索的持久单元的 bean 名称应与持久单元的名称匹配,应用程序引用它们,比如,在@PersistenceUnit@PersistenceContext注解。

LocalContainerEntityManagerFactoryBean

在基于 Spring 的使用 JPA 全功能的应用环境中,使用该选项。这个包含了 web 容器你比如 Tomcat 作为具有复杂的持续性要求的单独的应用和集成测试

LocalContainerEntityManagerFactoryBeanEntityManagerFactory完全控制配置和按需定制细粒度的适合的环境。LocalContainerEntityManagerFactoryBean创建基于 persistence.xml文件 的 PersistenceUnitInfo的实例,提供dataSourceLookup的策略,指定loadTimeWeaver。因此可以在 JNDI 外部使用自定义数据源和控制编织过程。下面的示例显示了一个典型的定义LocalContainerEntityManagerFactoryBean的 bean:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
    </bean>
</beans>

下面展示常见的 persistence.xml :

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>

<exclude-unlisted-classes/>快捷表明应该出现未扫描的注解的实体类。一个明确的 true 值指定<exclude-unlisted-classes>true</exclude-unlisted-classes/> 也意味着没有扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/> d触发扫描;然而,它是建议干脆省略 exclude-unlisted-classes元素如果你想扫描产生的实体类。

使用 LocalContainerEntityManagerFactoryBean 是最强大的 JPA 设置选项,允许丰富的在应用中本地配置。它支持连接到现有的 JDBC DataSource,支持 包括 本地和全局的事务,等等。然而,它还对运行时环境的有特殊的需求,比如需要 weaving-capable(可织入的)的类载入器,当持久性提供者要求字节码转换时。

此选项可能 Java EE 5 服务器中内置 JPA 功能冲突。在一个完整的 Java EE 5 的环境下,考虑从 JNDI 获取你的EntityManagerFactory。另外,LocalContainerEntityManagerFactoryBean定义中指定一个自定义 persistenceXmlLocation ,例如, META-INF/my-persistence.xml ,并且只包含一个描述符,这个名字在你的应用程序 jar 文件。因为 Java EE 5 服务器只查找默认META-INF/persistence.xml文件,它忽略了这些自定义持久性单元,从而避免与 Spring 驱动的 JPA 预先设置冲突。(例如,这适用于 Resin 3.1)。

什么时候需要载入时织入?

不是所有的 JPA 提供者需要 JVM 代理;Hibernate 就是这样的一个例子。如果你的提供者不需要一个代理或你有其他选择,如应用增强在构建时通过一个自定义的编译器或一个 ant 任务,此时不应使用载入时织入。

LoadTimeWeaver接口是一个 Spring 类,允许将 JPA ClassTransformer实例插入一个特定的方式,这取决于环境是 web 容器或应用程序服务器。通过一个代理来挂钩 ClassTransformers通常是无效的。代理工作在整个虚拟机并检查每一个加载类,通常是在生产服务器环境中不受欢迎的。

Spring 提供了许多 LoadTimeWeaver 各种环境的实现,允许ClassTransformer实例仅适用于每个类装入器,而不是每个 VM。

参考 AOP 章节“Spring配置”了解关于LoadTimeWeaver实现及其设置,包括泛型或定制各种平台(如 Tomcat、WebLogic、GlassFish、Resin 和JBoss)。

如上述所述部分,您可以配置一个context-wide(宽泛上下文的) LoadTimeWeaver使用context:load-time-weaver元素中的@EnableLoadTimeWeaving注释。这样一个全球织入是所有 JPA LocalContainerEntityManagerFactoryBeans自动捕捉到。这是设置加载时织入的首选方法,能自动识别出平台(WebLogic, GlassFish, Tomcat, Resin, JBoss 或者 VM 代理)和自动传播的织入到所有可织入的 bean 中:

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

不过,如果需要,可以手动通过 loadTimeWeaver属性指定一个专门的织入:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    </property>
</bean>

无论 LTW 如何配置,使用这种技术,JPA 应用程序依赖于器可以运行在目标平台(例:Tomcat)而不需要代理的基础设施。这是非常重要的,尤其是当托管的应用程序依赖于不同的 JPA 实现,因为 JPA 转换器只在类装入器级别,因此彼此是隔离。

Dealing with multiple persistence units 处理多个持久单元

对于依赖于多个持久单元的位置的应用程序,在类路径中,存储在不同的JAR 中,例如,Spring 提供PersistenceUnitManager作为中央存储库,以避免持久单元的发现过程,它可以是昂贵的。默认的实现允许多个位置被指定,稍后被通过持久单元名称检索。(默认情况下,路径搜索的的是META-INF/persistence.xml 文件。)

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
            <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
            <value>classpath:/my/package/**/custom-persistence.xml</value>
            <value>classpath*:META-INF/persistence.xml</value>
        </list>
    </property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        </map>
    </property>
    <!-- if no datasource is specified, use this one -->
    <property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="persistenceUnitName" value="myCustomUnit"/>
</bean>

默认的实现允许的自定义 PersistenceUnitInfo 实例,在他们传入 JPA 提供者之前,声明通过它的属性,影响所有的单元,或以编程方式,通过 PersistenceUnitPostProcessor,允许持久单元的选择。如果没有指定一个 PersistenceUnitManager,由LocalContainerEntityManagerFactoryBean内部创建和使用。

15.5.2 Implementing DAOs based on plain JPA 基于平常 JPA的 DAO 的实现

虽然 EntityManagerFactory 实例是线程安全的 , 但 EntityManager 不是。注入的 JPA EntityManager 的行为像一个 从应用服务器的 JNDI 环境中通过 JPA 规范定义的 EntityManager 。它代表所有调用当前事务EntityManager,如果是的话;否则,它在每次操作时返回新创建的EntityManager,使其线程安全。

通过注入EntityManagerFactoryEntityManager,对于编写平常 JPA 代码对 Spring 没有任何依赖。 Spring 可以理解@PersistenceUnit@PersistenceContext和注解在字段和方法层面,如果启动 PersistenceAnnotationBeanPostProcessor的话。普通的JPA DAO实现使用 @PersistenceUnit注解可能看起来像这样:

public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public Collection loadProductsByCategory(String category) {
        EntityManager em = this.emf.createEntityManager();
        try {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
        finally {
            if (em != null) {
                em.close();
            }
        }
    }
}

上面的 DAO 没有依赖 Spring ,但 任然非常符合 Spring 应用的上下文。此外,该 DAO 充分利用 EntityManagerFactory 默认注解:

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

作为一种明确替代定义 PersistenceAnnotationBeanPostProcessor,考虑使用 Spring context:annotation-config 在你的应用程序环境配置。这样做自动注册所有的 Spring 基于注释的配置标准处理器,包括CommonAnnotationBeanPostProcessor等等。

<beans>

    <!-- post-processors for all standard config annotations -->
    <context:annotation-config/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

这样的 DAO 的主要的问题是,它总是通过工厂创建一个新的 EntityManager 。你可以请求一个事务EntityManager(也被称为“共享 EntityManager ”因为它是一个共享的,线程安全的代理在实际事务 EntityManager 中)被注入而不是工厂来避免这种情况:

public class ProductDaoImpl implements ProductDao {

    @PersistenceContext
    private EntityManager em;

    public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return query.getResultList();
    }
}

@PersistenceContext注解有个可选属性type, 默认值是PersistenceContextType.TRANSACTION 。默认的是你需要接收共享的EntityManager代理。PersistenceContextType.EXTENDED,是一个完全不同的事情,这个结果对 EntityManager的扩展,它不是线程安全的,因此不能用于并发访问的组件如 Spring 管理单例 bean。扩展的 EntityManager 只能用在有状态的组件,例如,驻留在一个会话上,这样EntityManager 的生命周期不依赖于当前事务,而是完全取决于应用程序。

方法和字段级别的注入

注释表明依赖注入(如 @PersistenceUnit@PersistenceContext)可应用于类中的字段或方法,因此表现方法级别的注入和字段级别的注入。字段级别的注入是简洁和容易使用而方法级别允许进一步处理注入的依赖。在这两种情况下的成员可见性(公共,保护,私人)不要紧。

那类级别的注入呢?

在 Java EE 5 平台,它们是用来声明依赖而不是资源注入

注入EntityManager是 Spring 管理的(意识到正在进行的事务)。需要注意的是,尽管新的 DAO 实现使用一个 EntityManager 方法注入而不是一个EntityManagerFactory,在应用程序上下文的 XML 注释的用法无需改变。

这种 DAO 风格的主要优点是,它不仅取决于 Java Persistence API;(Java 持久性API),而无需引进任何 Spring 的类。此外,作为 JPA 注释更容易理解,注解可以被 Spring 容器自动应用。这是从非侵袭性的的角度看很具有吸引力,对于 JPA 的开发人员来说可能感觉更自然。

15.5.3 Transaction Management 事务管理

如果你还没有看过 12.5. Declarative transaction management 声明式事务管理强烈建议你看下,获取更多Spring 声明式事务的支持

执行服务的事务操作,使用 Spring 常见的声明式事务功能,举例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="myEmf"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="productDao" ref="myProductDao"/>
    </bean>

    <aop:config>
        <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="myTxManager">
        <tx:attributes>
            <tx:method name="increasePrice*" propagation="REQUIRED"/>
            <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

</beans>

Spring 的 JPA 允许配置 JpaTransactionManager来暴露 JPA 事务给 JDBC 访问代码从而能够访问同一个 JDBC DataSource,提供注册JpaDialect支持底层的 JDBC Connection检索。开箱即用,Spring 提供了 TopLink ,Hibernate 和 OpenJPA 的 JPA 实现的方言。请参阅下一节 JpaDialect机制。

15.5.4 JpaDialect

作为一个高级功能 JpaTemplate,JpaTransactionManagerAbstractEntityManagerFactoryBean子类支持自定义 JpaDialect,传递到 JpaDialect bean 属性。在这种情况下,DAO 未得到EntityManagerFactory的引用,而是一个完整的JpaTemplate实例(例如,传递到JpaDaoSupportJpaTemplate 属性)。JpaDialect实现可以使一些 Spring 支持的高级功能,通常取决于特定供应商的方式:

  • 应用特定的事务语义,如自定义隔离级别或事务超时)
  • 检索事务暴露于基于 JDBC 的 DAO 的 JDBC Connection)
  • PersistenceExceptions 到 Spring DataAccessExceptions的高级转换

这对于特殊事务语义和高级的异常转换来说是非常有价值的。默认实现使用(DefaultJpaDialect)不提供任何特殊功能,如果需要上面的功能,你必须指定适当的方言。

查看 JpaDialect 的 javadocs 获取更多如果使用 Spring JPA 的细节